﻿/******************************************************************************
 * SunnyUI 开源控件库、工具类库、扩展类库、多页面开发框架。
 * CopyRight (C) 2012-2022 ShenYongHua(沈永华).
 * QQ群：56829229 QQ：17612584 EMail：SunnyUI@QQ.Com
 *
 * Blog:   https://www.cnblogs.com/yhuse
 * Gitee:  https://gitee.com/yhuse/SunnyUI.git
 * GitHub: https://github.com/yhuse/SunnyUI
 *
 * SunnyUI can be used for free under the MIT license.
 * If you use this code, please keep this note.
 * 如果您使用此代码，请保留此说明。
 ******************************************************************************
 * 文件名称: USnowflakeID.cs
 * 文件说明: 雪花ID
 * 当前版本: V3.1
 * 创建日期: 2020-01-01
 *
 * 2020-01-01: V2.2 增加文件说明
******************************************************************************/

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Sunny.UI
{
    /*
     * https://blog.csdn.net/jackpk/article/details/78248351
     * https://www.cnblogs.com/sunyuliang/p/12161416.html
     * https://www.cnblogs.com/relucent/p/4955340.html
     * https://gitee.com/itsm/learning_example/commits/master/snowflake-%E9%9B%AA%E8%8A%B1Id/snowflake-%E9%9B%AA%E8%8A%B1Id
     * 雪花ID
     * Twitter_Snowflake
     * SnowFlake的结构如下(每部分用-分开)
     * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0
     * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截)得到的值），
     * 41位的时间截，可以使用69年，年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
     * 这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下下面程序IdWorker类的startTime属性）。
     * 10位的数据机器位，可以部署在1024个节点，包括5位datacenterId和5位workerId
     * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生4096个ID序号
     * 总共加起来刚好64位，为一个Long型。
     * SnowFlake的优点是，整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)，
     * 并且效率较高，经测试，SnowFlake单机每秒都能够产生出极限4,096,000个ID来
     */

    /// <summary>
    /// 雪花Id
    /// 组成格式为：时间戳|序号|数据中心ID|服务器ID
    /// </summary>
    public class SnowFlakeId
    {
        public static SnowFlakeId Instance()
        {
            if (instance == null)
            {
                instance = new SnowFlakeId();
            }

            return instance;
        }

        private static SnowFlakeId instance;

        /// <summary>
        /// 数据中心Id长度
        /// </summary>
        private int DataCenterIdBitLength { get; set; }

        /// <summary>
        /// 服务器Id长度
        /// </summary>
        private int WorkerIdBitLength { get; set; }

        /// <summary>
        /// 自增长序号长度
        /// </summary>
        private int SequenceBitLength { get; set; }

        /// <summary>
        /// 时间戳长度
        /// </summary>
        private int TimestampBitLength { get; set; }

        /// <summary>
        /// 数据中心Id最大值
        /// </summary>
        private long MaxDataCenterId { get; set; }

        /// <summary>
        /// 服务器Id最大值
        /// </summary>
        private long MaxWorkerId { get; set; }

        /// <summary>
        /// 自增序号最大值
        /// </summary>
        private long MaxSequence { get; set; }

        /// <summary>
        /// 时间戳最大值
        /// </summary>
        private long MaxTimestamp { get; set; }

        private DateTime MaxUtcTime { get { return Jan1st1970.AddMilliseconds(MaxTimestamp + StartTimestamp); } }

        /// <summary>
        /// 上次生成ID
        /// </summary>
        public SnowId LastSnowId { get; private set; }

        /// <summary>
        /// 数据中心Id
        /// </summary>
        public long DataCenterId { get; private set; }

        /// <summary>
        /// 服务器Id
        /// </summary>
        public long WorkerId { get; private set; }


        public SnowFlakeId() : this(16, 0, 0, 0, 0)
        {

        }

        public SnowFlakeId(long dataCenterId, long workerId) : this(12, 5, 5, dataCenterId, workerId)
        {

        }

        public SnowFlakeId(int sequenceBitLength, int datacenterIdBitLength, int workerIdBitLength, long dataCenterId, long workerId)
        {
            this.SequenceBitLength = sequenceBitLength;
            this.DataCenterIdBitLength = datacenterIdBitLength;
            this.WorkerIdBitLength = workerIdBitLength;
            this.TimestampBitLength = Marshal.SizeOf(typeof(long)) - (SequenceBitLength + DataCenterIdBitLength + WorkerIdBitLength);

            this.MaxSequence = GetBitLengthMaxValue(this.SequenceBitLength);
            this.MaxDataCenterId = GetBitLengthMaxValue(this.DataCenterIdBitLength);
            this.MaxWorkerId = GetBitLengthMaxValue(this.WorkerIdBitLength);
            this.MaxTimestamp = GetBitLengthMaxValue(this.TimestampBitLength);

            if (dataCenterId > MaxDataCenterId) throw new OverflowException(nameof(dataCenterId));
            if (workerId > MaxWorkerId) throw new OverflowException(nameof(workerId));

            this.DataCenterId = dataCenterId;
            this.WorkerId = workerId;

            this.LastSnowId = new SnowId()
            {
                Timestamp = 0L,
                Sequence = 0L,
                DataCenterId = this.DataCenterId,
                WorkerId = this.WorkerId,
            };

        }

        /// <summary>
        /// 生成ID
        /// </summary>
        /// <returns></returns>
        public long Next()
        {
            lock (LastSnowId)
            {
                long timestamp = GetCurrentTimestamp();
                if (timestamp > LastSnowId.Timestamp) //时间戳改变，毫秒内序列重置
                {
                    LastSnowId.Sequence = 0L;
                }
                else if (timestamp == LastSnowId.Timestamp) //如果是同一时间生成的，则进行毫秒内序列
                {
                    LastSnowId.Sequence += 1L;
                    if (LastSnowId.Sequence > MaxSequence) //毫秒内序列溢出
                    {
                        LastSnowId.Sequence = 0L;
                        timestamp = GetNextTimestamp(LastSnowId.Timestamp); //阻塞到下一个毫秒,获得新的时间戳
                    }
                }
                else   //当前时间小于上一次ID生成的时间戳，证明系统时钟被回拨，此时需要做回拨处理
                {
                    LastSnowId.Sequence += 1L;
                    timestamp = LastSnowId.Timestamp; //停留在最后一次时间戳上，等待系统时间追上后即完全度过了时钟回拨问题。
                    if (LastSnowId.Sequence > MaxSequence)  //毫秒内序列溢出
                    {
                        LastSnowId.Sequence = 0L;
                        timestamp = LastSnowId.Timestamp + 1L;   //直接进位到下一个毫秒                          
                    }
                    //throw new Exception(string.Format("Clock moved backwards.  Refusing to generate id for {0} milliseconds", lastTimestamp - timestamp));
                }
                LastSnowId.Timestamp = timestamp;       //上次生成ID的时间戳
                LastSnowId.Id = Next(LastSnowId);
                return LastSnowId.Id;
            }
        }

        /// <summary>
        /// 生成ID
        /// </summary>
        /// <param name="timestamp">时间戳</param>
        /// <param name="sequence">序号</param>
        /// <returns></returns>
        public long Next(SnowId snowId)
        {
            //移位并通过或运算拼到一起组成64位的ID
            var id = (snowId.Timestamp << (SequenceBitLength + DataCenterIdBitLength + WorkerIdBitLength))
                    | (snowId.Sequence << (DataCenterIdBitLength + WorkerIdBitLength))
                    | (snowId.DataCenterId << WorkerIdBitLength)
                    | (snowId.WorkerId);

            return id;
        }

        /// <summary>
        /// 解析雪花ID
        /// </summary>
        /// <returns></returns>
        public SnowId AnalyzeId(long Id)
        {
            var snowIdClass = new SnowId()
            {
                Id = Id,
                Timestamp = Id >> (SequenceBitLength + DataCenterIdBitLength + WorkerIdBitLength),
                Sequence = Id >> (DataCenterIdBitLength + WorkerIdBitLength) & MaxSequence,
                DataCenterId = Id >> WorkerIdBitLength & MaxDataCenterId,
                WorkerId = Id & MaxWorkerId
            };
            return snowIdClass;
        }

        private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        // 开始时间戳 
        private static readonly long StartTimestamp = (long)(new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc) - Jan1st1970).TotalMilliseconds;

        /// <summary>
        /// 阻塞到下一个毫秒，直到获得新的时间戳
        /// </summary>
        /// <param name="lastTimestamp">上次生成ID的时间戳</param>
        /// <returns>当前时间戳</returns>
        private static long GetNextTimestamp(long lastTimestamp)
        {
            long timestamp = GetCurrentTimestamp();
            while (timestamp <= lastTimestamp)
            {
                timestamp = GetCurrentTimestamp();
            }
            return timestamp;
        }

        /// <summary>
        /// 获取当前时间戳
        /// </summary>
        /// <returns></returns>
        private static long GetCurrentTimestamp()
        {
            return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds - StartTimestamp;
        }

        private static long GetBitLengthMaxValue(int length)
        {
            return -1L ^ (-1L << length);
        }


        /// <summary>
        /// 雪花ID结构
        /// </summary>
        public class SnowId
        {
            /// <summary>
            /// ID
            /// </summary>
            public long Id { get; set; }
            /// <summary>
            /// 时间戳
            /// </summary>
            public long Timestamp { get; set; }

            /// <summary>
            /// Unix时间戳
            /// </summary>
            public long UnixTimestamp { get { return Timestamp + StartTimestamp; } }

            /// <summary>
            /// 生成时间
            /// </summary>
            public DateTime UtcDateTime { get { return Jan1st1970.AddMilliseconds(UnixTimestamp); } }

            /// <summary>
            /// 自增长序号
            /// </summary>
            public long Sequence { get; set; }

            /// <summary>
            /// 数据中心Id
            /// </summary>
            public long DataCenterId { get; set; }

            /// <summary>
            /// 服务器Id
            /// </summary>
            public long WorkerId { get; set; }

            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"));
                sb.Append(", ");
                sb.Append(DataCenterId);
                sb.Append(", ");
                sb.Append(WorkerId);
                sb.Append(", ");
                sb.Append(Sequence);
                return sb.ToString();
            }
        }
    }
}